feat: basic ffi server to support other languages (#34)
* wip * wip * wip * wip not a fan of the way I do handles .. * errors and async semantic * fix example
This commit is contained in:
Generated
+14
@@ -662,6 +662,20 @@ dependencies = [
|
|||||||
"url",
|
"url",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "livekit-ffi"
|
||||||
|
version = "0.1.1"
|
||||||
|
dependencies = [
|
||||||
|
"lazy_static",
|
||||||
|
"livekit",
|
||||||
|
"parking_lot",
|
||||||
|
"prost",
|
||||||
|
"prost-build",
|
||||||
|
"prost-types",
|
||||||
|
"thiserror",
|
||||||
|
"tokio",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "livekit-utils"
|
name = "livekit-utils"
|
||||||
version = "0.1.1"
|
version = "0.1.1"
|
||||||
|
|||||||
+2
-1
@@ -1,8 +1,9 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
members = [
|
members = [
|
||||||
"livekit",
|
"livekit",
|
||||||
"livekit-webrtc",
|
|
||||||
"livekit-utils",
|
"livekit-utils",
|
||||||
|
"livekit-ffi",
|
||||||
|
"livekit-webrtc",
|
||||||
"webrtc-sys"
|
"webrtc-sys"
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
Generated
+14
-14
@@ -4,9 +4,9 @@ version = 3
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ab_glyph"
|
name = "ab_glyph"
|
||||||
version = "0.2.18"
|
version = "0.2.19"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4dcdbc68024b653943864d436fe8a24b028095bc1cf91a8926f8241e4aaffe59"
|
checksum = "e5568a4aa5ba8adf5175c5c460b030e27d8893412976cc37bef0e4fbc16cfbba"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ab_glyph_rasterizer",
|
"ab_glyph_rasterizer",
|
||||||
"owned_ttf_parser",
|
"owned_ttf_parser",
|
||||||
@@ -129,9 +129,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "atomic_refcell"
|
name = "atomic_refcell"
|
||||||
version = "0.1.8"
|
version = "0.1.9"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "73b5e5f48b927f04e952dedc932f31995a65a0bf65ec971c74436e51bf6e970d"
|
checksum = "857253367827bd9d0fd973f0ef15506a96e79e41b0ad7aa691203a4e3214f6c8"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "autocfg"
|
name = "autocfg"
|
||||||
@@ -227,9 +227,9 @@ checksum = "dfb24e866b15a1af2a1b663f10c6b6b8f397a84aadb828f12e5b289ec23a3a3c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bzip2"
|
name = "bzip2"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6afcd980b5f3a45017c57e57a2fcccbb351cc43a356ce117ef760ef8052b89b0"
|
checksum = "bdb116a6ef3f6c3698828873ad02c3014b3c85cadb88496095628e3ef1e347f8"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bzip2-sys",
|
"bzip2-sys",
|
||||||
"libc",
|
"libc",
|
||||||
@@ -1714,9 +1714,9 @@ checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "owned_ttf_parser"
|
name = "owned_ttf_parser"
|
||||||
version = "0.17.1"
|
version = "0.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "18904d3c65493a9f0d7542293d1a7f69bfdc309a6b9ef4f46dc3e58b0577edc5"
|
checksum = "2a5f3c7ca08b6879e7965fb25e24d1f5eeb32ea73f9ad99b3854778a38c57e93"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
]
|
]
|
||||||
@@ -1827,9 +1827,9 @@ checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prettyplease"
|
name = "prettyplease"
|
||||||
version = "0.1.22"
|
version = "0.1.23"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2c8992a85d8e93a28bdf76137db888d3874e3b230dee5ed8bebac4c9f7617773"
|
checksum = "e97e3215779627f01ee256d2fad52f3d95e8e1c11e9fc6fd08f7cd455d5d5c78"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"syn",
|
"syn",
|
||||||
@@ -2484,9 +2484,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tokio"
|
name = "tokio"
|
||||||
version = "1.23.0"
|
version = "1.24.1"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46"
|
checksum = "1d9f76183f91ecfb55e1d7d5602bd1d979e38a3a522fe900241cf195624d67ae"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"autocfg",
|
"autocfg",
|
||||||
"bytes",
|
"bytes",
|
||||||
@@ -2606,9 +2606,9 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ttf-parser"
|
name = "ttf-parser"
|
||||||
version = "0.17.1"
|
version = "0.18.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "375812fa44dab6df41c195cd2f7fecb488f6c09fbaafb62807488cefab642bff"
|
checksum = "dbf554b6e535f9a160b2ed4ea83f99000f21cbc0a693df26e258eaf2c226a151"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tungstenite"
|
name = "tungstenite"
|
||||||
|
|||||||
@@ -362,7 +362,7 @@ impl App {
|
|||||||
));
|
));
|
||||||
ui.label(format!(
|
ui.label(format!(
|
||||||
"ParticipantCount: {:?}",
|
"ParticipantCount: {:?}",
|
||||||
room.session().participants().read().len() + 1
|
room.session().participants().len() + 1
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -411,7 +411,6 @@ impl App {
|
|||||||
let name = self.state.room.lock().as_ref().and_then(|room| {
|
let name = self.state.room.lock().as_ref().and_then(|room| {
|
||||||
room.session()
|
room.session()
|
||||||
.participants()
|
.participants()
|
||||||
.read()
|
|
||||||
.get(participant_sid)
|
.get(participant_sid)
|
||||||
.map(|p| p.name())
|
.map(|p| p.name())
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,3 +9,15 @@ repository = "https://github.com/livekit/client-sdk-rust"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
livekit = { path = "../livekit", version = "0.1.1" }
|
livekit = { path = "../livekit", version = "0.1.1" }
|
||||||
tokio = { version = "1.0", features = ["full"] }
|
tokio = { version = "1.0", features = ["full"] }
|
||||||
|
parking_lot = { version = "0.12.1", features = ["send_guard"] }
|
||||||
|
prost = "0.11.0"
|
||||||
|
prost-types = "0.11.1"
|
||||||
|
lazy_static = "1.4.0"
|
||||||
|
thiserror = "1.0.38"
|
||||||
|
|
||||||
|
[build-dependencies]
|
||||||
|
prost-build = { version = "0.11.1" }
|
||||||
|
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "staticlib"]
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
use std::io::Result;
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
prost_build::compile_protos(&["protocol/ffi.proto"], &["protocol/"])?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
package livekit;
|
||||||
|
option csharp_namespace = "LiveKit.Proto";
|
||||||
|
|
||||||
|
/// IPC
|
||||||
|
|
||||||
|
/// # Safety
|
||||||
|
/// The foreign language is responsable for disposing an handle
|
||||||
|
/// Forgetting to dispose the handle may lead to memory leaks
|
||||||
|
/// Messages bellow can contain an FFIHandle
|
||||||
|
message FFIHandleId { uint32 id = 1; }
|
||||||
|
|
||||||
|
message FFIRequest {
|
||||||
|
oneof message {
|
||||||
|
InitializeRequest configure = 1;
|
||||||
|
ConnectRequest async_connect = 2;
|
||||||
|
DisconnectRequest async_disconnect = 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message FFIResponse {
|
||||||
|
oneof message {
|
||||||
|
ConnectResponse async_connect = 1;
|
||||||
|
RoomEvent room_event = 2;
|
||||||
|
TrackEvent track_event = 3;
|
||||||
|
ParticipantEvent participant_event = 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message InitializeRequest { uint64 callback_ptr = 1; }
|
||||||
|
|
||||||
|
message ConnectRequest {
|
||||||
|
string url = 1;
|
||||||
|
string token = 2;
|
||||||
|
RoomOptions options = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DisconnectRequest { string room_sid = 1; }
|
||||||
|
|
||||||
|
message ConnectResponse {
|
||||||
|
bool success = 1;
|
||||||
|
optional RoomInfo room = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Models
|
||||||
|
|
||||||
|
message RoomOptions {
|
||||||
|
bool auto_subscribe = 1;
|
||||||
|
bool adaptive_stream = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message RoomInfo {
|
||||||
|
string sid = 1;
|
||||||
|
string name = 2;
|
||||||
|
ParticipantInfo local_participant = 3;
|
||||||
|
repeated ParticipantInfo participants = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
message ParticipantInfo {
|
||||||
|
string sid = 1;
|
||||||
|
string name = 2;
|
||||||
|
string identity = 3;
|
||||||
|
string metadata = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TrackKind {
|
||||||
|
KIND_UNKNOWN = 0;
|
||||||
|
KIND_AUDIO = 1;
|
||||||
|
KIND_VIDEO = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum StreamState {
|
||||||
|
STATE_UNKNOWN = 0;
|
||||||
|
STATE_ACTIVE = 1;
|
||||||
|
STATE_PAUSED = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrackPublicationInfo {
|
||||||
|
string sid = 1;
|
||||||
|
string name = 2;
|
||||||
|
TrackKind kind = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrackInfo {
|
||||||
|
string sid = 1;
|
||||||
|
string name = 2;
|
||||||
|
TrackKind kind = 3;
|
||||||
|
StreamState state = 4;
|
||||||
|
bool muted = 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Room Events
|
||||||
|
|
||||||
|
message RoomEvent {
|
||||||
|
string room_sid = 1;
|
||||||
|
oneof message {
|
||||||
|
ParticipantConnected participant_connected = 2;
|
||||||
|
ParticipantDisconnected participant_disconnected = 3;
|
||||||
|
TrackPublished track_published = 4;
|
||||||
|
TrackUnpublished track_unpublished = 5;
|
||||||
|
TrackSubscribed track_subscribed = 6;
|
||||||
|
TrackUnsubscribed track_unsubscribed = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message ParticipantConnected { ParticipantInfo info = 1; }
|
||||||
|
|
||||||
|
message ParticipantDisconnected { ParticipantInfo info = 1; }
|
||||||
|
|
||||||
|
message TrackPublished {
|
||||||
|
TrackPublicationInfo publication = 1;
|
||||||
|
string participant_sid = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrackUnpublished {
|
||||||
|
TrackPublicationInfo publication = 1;
|
||||||
|
string participant_sid = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Publication isn't needed for subscription events on the FFI
|
||||||
|
// The FFI will retrieve the publication using the Track sid
|
||||||
|
message TrackSubscribed {
|
||||||
|
// TrackPublicationInfo publication = 1;
|
||||||
|
TrackInfo track = 1;
|
||||||
|
string participant_sid = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message TrackUnsubscribed {
|
||||||
|
// TrackPublicationInfo publication = 1;
|
||||||
|
TrackInfo track = 1;
|
||||||
|
string participant_sid = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message DataReceived {
|
||||||
|
FFIHandleId handle = 1;
|
||||||
|
string participant_sid = 2;
|
||||||
|
uint64 data_ptr = 3;
|
||||||
|
uint64 data_size = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Track Events
|
||||||
|
|
||||||
|
message TrackEvent {
|
||||||
|
string track_sid = 1;
|
||||||
|
oneof message { FrameReceived frame_received = 2; }
|
||||||
|
}
|
||||||
|
|
||||||
|
message FrameReceived {
|
||||||
|
VideoFrame frame = 1;
|
||||||
|
VideoFrameBuffer frame_buffer = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VideoFrame {
|
||||||
|
int32 width = 1;
|
||||||
|
int32 height = 2;
|
||||||
|
uint32 size = 3;
|
||||||
|
uint32 id = 4; // uint16
|
||||||
|
int64 timestamp_us = 5;
|
||||||
|
int64 ntp_time_ms = 6;
|
||||||
|
uint32 transport_frame_id = 7;
|
||||||
|
uint32 timestamp = 8;
|
||||||
|
VideoRotation rotation = 9;
|
||||||
|
}
|
||||||
|
|
||||||
|
message VideoFrameBuffer {
|
||||||
|
FFIHandleId handle = 1;
|
||||||
|
VideoFrameBufferType buffer_type = 2;
|
||||||
|
int32 width = 3;
|
||||||
|
int32 height = 4;
|
||||||
|
oneof buffer {
|
||||||
|
PlanarYuvBuffer yuv = 5;
|
||||||
|
BiplanarYuvBuffer bi_yuv = 6;
|
||||||
|
NativeBuffer native = 7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
message PlanarYuvBuffer {
|
||||||
|
int32 chroma_width = 1;
|
||||||
|
int32 chroma_height = 2;
|
||||||
|
int32 stride_y = 3;
|
||||||
|
int32 stride_u = 4;
|
||||||
|
int32 stride_v = 5;
|
||||||
|
|
||||||
|
// *const u8 or *const u16
|
||||||
|
uint64 data_y_ptr = 6;
|
||||||
|
uint64 data_u_ptr = 7;
|
||||||
|
uint64 data_v_ptr = 8;
|
||||||
|
}
|
||||||
|
|
||||||
|
message BiplanarYuvBuffer {
|
||||||
|
int32 chroma_width = 1;
|
||||||
|
int32 chroma_height = 2;
|
||||||
|
int32 stride_y = 3;
|
||||||
|
int32 stride_uv = 4;
|
||||||
|
|
||||||
|
uint64 data_y_ptr = 5;
|
||||||
|
uint64 data_uv_ptr = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
message NativeBuffer {
|
||||||
|
// TODO(theomonnom): Expose graphic context?
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VideoFrameBufferType {
|
||||||
|
NATIVE = 0;
|
||||||
|
I420 = 1;
|
||||||
|
I420A = 2;
|
||||||
|
I422 = 3;
|
||||||
|
I444 = 4;
|
||||||
|
I010 = 5;
|
||||||
|
NV12 = 6;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum VideoRotation {
|
||||||
|
VIDEO_ROTATION_0 = 0;
|
||||||
|
VIDEO_ROTATION_90 = 1;
|
||||||
|
VIDEO_ROTATION_180 = 2;
|
||||||
|
VIDEO_ROTATION_270 = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Participant Events
|
||||||
|
|
||||||
|
message ParticipantEvent { string participant_sid = 1; }
|
||||||
@@ -1 +1,5 @@
|
|||||||
|
mod proto {
|
||||||
|
include!(concat!(env!("OUT_DIR"), "/livekit.rs"));
|
||||||
|
}
|
||||||
|
|
||||||
|
mod server;
|
||||||
|
|||||||
@@ -0,0 +1,270 @@
|
|||||||
|
use crate::{proto, server::FFIHandleId};
|
||||||
|
use livekit::{
|
||||||
|
prelude::*,
|
||||||
|
webrtc::video_frame_buffer::{
|
||||||
|
BiplanarYuv8Buffer, BiplanarYuvBuffer, I010Buffer, I420ABuffer, I420Buffer, I422Buffer,
|
||||||
|
I444Buffer, NV12Buffer, PlanarYuv16BBuffer, PlanarYuv8Buffer, PlanarYuvBuffer,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
impl From<FFIHandleId> for proto::FfiHandleId {
|
||||||
|
fn from(id: FFIHandleId) -> Self {
|
||||||
|
Self { id: id as u32 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_participant_into {
|
||||||
|
($p:ty) => {
|
||||||
|
impl From<$p> for proto::ParticipantInfo {
|
||||||
|
fn from(p: $p) -> Self {
|
||||||
|
Self {
|
||||||
|
name: p.name(),
|
||||||
|
sid: p.sid().to_string(),
|
||||||
|
identity: p.identity().to_string(),
|
||||||
|
metadata: p.metadata(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_participant_into!(&Arc<LocalParticipant>);
|
||||||
|
impl_participant_into!(&Arc<RemoteParticipant>);
|
||||||
|
impl_participant_into!(&Participant);
|
||||||
|
|
||||||
|
macro_rules! impl_publication_into {
|
||||||
|
($p:ty) => {
|
||||||
|
impl From<$p> for proto::TrackPublicationInfo {
|
||||||
|
fn from(p: $p) -> Self {
|
||||||
|
Self {
|
||||||
|
name: p.name(),
|
||||||
|
sid: p.sid().to_string(),
|
||||||
|
kind: proto::TrackKind::from(p.kind()).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_publication_into!(&LocalTrackPublication);
|
||||||
|
impl_publication_into!(&RemoteTrackPublication);
|
||||||
|
impl_publication_into!(&TrackPublication);
|
||||||
|
|
||||||
|
macro_rules! impl_track_into {
|
||||||
|
($t:ty) => {
|
||||||
|
impl From<$t> for proto::TrackInfo {
|
||||||
|
fn from(track: $t) -> Self {
|
||||||
|
Self {
|
||||||
|
name: track.name(),
|
||||||
|
state: proto::StreamState::from(track.stream_state()).into(),
|
||||||
|
sid: track.sid().to_string(),
|
||||||
|
kind: proto::TrackKind::from(track.kind()).into(),
|
||||||
|
muted: track.muted(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_track_into!(&LocalAudioTrack);
|
||||||
|
impl_track_into!(&LocalVideoTrack);
|
||||||
|
impl_track_into!(&RemoteAudioTrack);
|
||||||
|
impl_track_into!(&RemoteVideoTrack);
|
||||||
|
impl_track_into!(&TrackHandle);
|
||||||
|
impl_track_into!(&LocalTrackHandle);
|
||||||
|
impl_track_into!(&RemoteTrackHandle);
|
||||||
|
|
||||||
|
impl From<TrackKind> for proto::TrackKind {
|
||||||
|
fn from(kind: TrackKind) -> Self {
|
||||||
|
match kind {
|
||||||
|
TrackKind::Unknown => proto::TrackKind::KindUnknown,
|
||||||
|
TrackKind::Audio => proto::TrackKind::KindAudio,
|
||||||
|
TrackKind::Video => proto::TrackKind::KindVideo,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<StreamState> for proto::StreamState {
|
||||||
|
fn from(state: StreamState) -> Self {
|
||||||
|
match state {
|
||||||
|
StreamState::Unknown => Self::StateUnknown,
|
||||||
|
StreamState::Active => Self::StateActive,
|
||||||
|
StreamState::Paused => Self::StatePaused,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl proto::RoomEvent {
|
||||||
|
pub fn from(room_sid: impl Into<String>, event: RoomEvent) -> Option<Self> {
|
||||||
|
let message = match event {
|
||||||
|
RoomEvent::ParticipantConnected(participant) => Some(
|
||||||
|
proto::room_event::Message::ParticipantConnected(proto::ParticipantConnected {
|
||||||
|
info: Some((&participant).into()),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
RoomEvent::ParticipantDisconnected(participant) => {
|
||||||
|
Some(proto::room_event::Message::ParticipantDisconnected(
|
||||||
|
proto::ParticipantDisconnected {
|
||||||
|
info: Some((&participant).into()),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
RoomEvent::TrackPublished {
|
||||||
|
publication,
|
||||||
|
participant,
|
||||||
|
} => Some(proto::room_event::Message::TrackPublished(
|
||||||
|
proto::TrackPublished {
|
||||||
|
participant_sid: participant.sid().to_string(),
|
||||||
|
publication: Some((&publication).into()),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
RoomEvent::TrackUnpublished {
|
||||||
|
publication,
|
||||||
|
participant,
|
||||||
|
} => Some(proto::room_event::Message::TrackUnpublished(
|
||||||
|
proto::TrackUnpublished {
|
||||||
|
participant_sid: participant.sid().to_string(),
|
||||||
|
publication: Some((&publication).into()),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
RoomEvent::TrackSubscribed {
|
||||||
|
track,
|
||||||
|
publication: _,
|
||||||
|
participant,
|
||||||
|
} => Some(proto::room_event::Message::TrackSubscribed(
|
||||||
|
proto::TrackSubscribed {
|
||||||
|
participant_sid: participant.sid().to_string(),
|
||||||
|
track: Some((&track).into()),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
RoomEvent::TrackUnsubscribed {
|
||||||
|
track,
|
||||||
|
publication: _,
|
||||||
|
participant,
|
||||||
|
} => Some(proto::room_event::Message::TrackUnsubscribed(
|
||||||
|
proto::TrackUnsubscribed {
|
||||||
|
participant_sid: participant.sid().to_string(),
|
||||||
|
track: Some((&track).into()),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
message.map(|message| proto::RoomEvent {
|
||||||
|
room_sid: room_sid.into(),
|
||||||
|
message: Some(message),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VideoRotation> for proto::VideoRotation {
|
||||||
|
fn from(rotation: VideoRotation) -> proto::VideoRotation {
|
||||||
|
match rotation {
|
||||||
|
VideoRotation::VideoRotation0 => Self::VideoRotation0,
|
||||||
|
VideoRotation::VideoRotation90 => Self::VideoRotation90,
|
||||||
|
VideoRotation::VideoRotation180 => Self::VideoRotation180,
|
||||||
|
VideoRotation::VideoRotation270 => Self::VideoRotation270,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VideoFrame> for proto::VideoFrame {
|
||||||
|
fn from(frame: VideoFrame) -> Self {
|
||||||
|
Self {
|
||||||
|
width: frame.width(),
|
||||||
|
height: frame.height(),
|
||||||
|
size: frame.size(),
|
||||||
|
id: frame.id() as u32,
|
||||||
|
timestamp_us: frame.timestamp_us(),
|
||||||
|
ntp_time_ms: frame.ntp_time_ms(),
|
||||||
|
transport_frame_id: frame.transport_frame_id(),
|
||||||
|
timestamp: frame.timestamp(),
|
||||||
|
rotation: proto::VideoRotation::from(frame.rotation()).into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<VideoFrameBufferType> for proto::VideoFrameBufferType {
|
||||||
|
fn from(buffer_type: VideoFrameBufferType) -> Self {
|
||||||
|
match buffer_type {
|
||||||
|
VideoFrameBufferType::Native => Self::Native,
|
||||||
|
VideoFrameBufferType::I420 => Self::I420,
|
||||||
|
VideoFrameBufferType::I420A => Self::I420a,
|
||||||
|
VideoFrameBufferType::I422 => Self::I422,
|
||||||
|
VideoFrameBufferType::I444 => Self::I444,
|
||||||
|
VideoFrameBufferType::I010 => Self::I010,
|
||||||
|
VideoFrameBufferType::NV12 => Self::Nv12,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_yuv_into {
|
||||||
|
($b:ty) => {
|
||||||
|
impl From<$b> for proto::PlanarYuvBuffer {
|
||||||
|
fn from(buffer: $b) -> Self {
|
||||||
|
Self {
|
||||||
|
chroma_width: buffer.chroma_width(),
|
||||||
|
chroma_height: buffer.chroma_height(),
|
||||||
|
stride_y: buffer.stride_y(),
|
||||||
|
stride_u: buffer.stride_u(),
|
||||||
|
stride_v: buffer.stride_v(),
|
||||||
|
data_y_ptr: buffer.data_y().as_ptr() as u64,
|
||||||
|
data_u_ptr: buffer.data_u().as_ptr() as u64,
|
||||||
|
data_v_ptr: buffer.data_v().as_ptr() as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_yuv_into!(&I420Buffer);
|
||||||
|
impl_yuv_into!(&I420ABuffer);
|
||||||
|
impl_yuv_into!(&I422Buffer);
|
||||||
|
impl_yuv_into!(&I444Buffer);
|
||||||
|
impl_yuv_into!(&I010Buffer);
|
||||||
|
|
||||||
|
macro_rules! impl_biyuv_into {
|
||||||
|
($b:ty) => {
|
||||||
|
impl From<$b> for proto::BiplanarYuvBuffer {
|
||||||
|
fn from(buffer: $b) -> Self {
|
||||||
|
Self {
|
||||||
|
chroma_width: buffer.chroma_width(),
|
||||||
|
chroma_height: buffer.chroma_height(),
|
||||||
|
stride_y: buffer.stride_y(),
|
||||||
|
stride_uv: buffer.stride_uv(),
|
||||||
|
data_y_ptr: buffer.data_y().as_ptr() as u64,
|
||||||
|
data_uv_ptr: buffer.data_uv().as_ptr() as u64,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl_biyuv_into!(&NV12Buffer);
|
||||||
|
|
||||||
|
impl proto::VideoFrameBuffer {
|
||||||
|
pub fn from(handle_id: FFIHandleId, buffer: &VideoFrameBuffer) -> Self {
|
||||||
|
Self {
|
||||||
|
handle: Some(handle_id.into()),
|
||||||
|
buffer_type: proto::VideoFrameBufferType::from(buffer.buffer_type()).into(),
|
||||||
|
width: buffer.width(),
|
||||||
|
height: buffer.height(),
|
||||||
|
buffer: Some(match &buffer {
|
||||||
|
VideoFrameBuffer::Native(_) => {
|
||||||
|
proto::video_frame_buffer::Buffer::Native(proto::NativeBuffer {})
|
||||||
|
}
|
||||||
|
VideoFrameBuffer::I420(i420) => proto::video_frame_buffer::Buffer::Yuv(i420.into()),
|
||||||
|
VideoFrameBuffer::I420A(i420a) => {
|
||||||
|
proto::video_frame_buffer::Buffer::Yuv(i420a.into())
|
||||||
|
}
|
||||||
|
VideoFrameBuffer::I422(i422) => proto::video_frame_buffer::Buffer::Yuv(i422.into()),
|
||||||
|
VideoFrameBuffer::I444(i444) => proto::video_frame_buffer::Buffer::Yuv(i444.into()),
|
||||||
|
VideoFrameBuffer::I010(i010) => proto::video_frame_buffer::Buffer::Yuv(i010.into()),
|
||||||
|
VideoFrameBuffer::NV12(nv12) => {
|
||||||
|
proto::video_frame_buffer::Buffer::BiYuv(nv12.into())
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,221 @@
|
|||||||
|
use crate::{
|
||||||
|
proto, proto::ffi_request::Message as FFIRequest, proto::ffi_response::Message as FFIResponse,
|
||||||
|
};
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use livekit::prelude::*;
|
||||||
|
use livekit::webrtc::media_stream::OnFrameHandler;
|
||||||
|
use parking_lot::{Mutex, RwLock};
|
||||||
|
use prost::Message;
|
||||||
|
use std::any::Any;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::panic;
|
||||||
|
use std::slice;
|
||||||
|
use std::sync::atomic::AtomicU32;
|
||||||
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
mod conversion;
|
||||||
|
|
||||||
|
#[derive(Error, Debug)]
|
||||||
|
pub enum FFIError {
|
||||||
|
#[error("the FFIServer isn't configured")]
|
||||||
|
NotConfigured,
|
||||||
|
#[error("failed to execute the ffi callback")]
|
||||||
|
CallbackFailed,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type FFIHandleId = u32;
|
||||||
|
pub type FFIHandle = Box<dyn Any + Send + Sync>;
|
||||||
|
|
||||||
|
type CallbackFn = unsafe extern "C" fn(*const u8, usize); // This "C" callback must be threadsafe
|
||||||
|
|
||||||
|
lazy_static! {
|
||||||
|
static ref FFI_SERVER: FFIServer = FFIServer::default();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct FFIConfig {
|
||||||
|
callback_fn: CallbackFn,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// To use the FFI, the foreign language and the FFI server must share
|
||||||
|
/// the same memory space
|
||||||
|
pub struct FFIServer {
|
||||||
|
// Object owned by the foreign language
|
||||||
|
// The foreign language is responsible for freeing this memory
|
||||||
|
ffi_owned: RwLock<HashMap<FFIHandleId, FFIHandle>>,
|
||||||
|
next_handle: AtomicU32, // FFIHandle
|
||||||
|
|
||||||
|
rooms: RwLock<HashMap<RoomSid, Room>>,
|
||||||
|
async_runtime: tokio::runtime::Runtime,
|
||||||
|
initialized: AtomicBool,
|
||||||
|
config: Mutex<Option<FFIConfig>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FFIServer {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
ffi_owned: RwLock::new(HashMap::new()),
|
||||||
|
next_handle: Default::default(),
|
||||||
|
rooms: RwLock::new(HashMap::new()),
|
||||||
|
async_runtime: tokio::runtime::Builder::new_multi_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
initialized: Default::default(),
|
||||||
|
config: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FFIServer {
|
||||||
|
pub fn next_handle_id(&self) -> FFIHandleId {
|
||||||
|
self.next_handle.fetch_add(1, Ordering::SeqCst) as FFIHandleId
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_handle(&self, handle_id: FFIHandleId, handle: FFIHandle) {
|
||||||
|
self.ffi_owned.write().insert(handle_id, handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn release_handle(&self, handle_id: FFIHandleId) -> Option<FFIHandle> {
|
||||||
|
self.ffi_owned.write().remove(&handle_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn send_response(&self, message: FFIResponse) -> Result<(), FFIError> {
|
||||||
|
if !self.initialized.load(Ordering::SeqCst) {
|
||||||
|
Err(FFIError::NotConfigured)?
|
||||||
|
}
|
||||||
|
|
||||||
|
let message = proto::FfiResponse {
|
||||||
|
message: Some(message),
|
||||||
|
}
|
||||||
|
.encode_to_vec();
|
||||||
|
|
||||||
|
let callback_fn = self.config.lock().as_ref().unwrap().callback_fn;
|
||||||
|
if let Err(err) = panic::catch_unwind(|| unsafe {
|
||||||
|
callback_fn(message.as_ptr(), message.len());
|
||||||
|
}) {
|
||||||
|
eprintln!("panic when sending ffi response: {:?}", err);
|
||||||
|
Err(FFIError::CallbackFailed)?
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn on_request_received(&self, message: FFIRequest) -> Result<(), FFIError> {
|
||||||
|
if let FFIRequest::Configure(ref init) = message {
|
||||||
|
self.initialized.store(true, Ordering::SeqCst);
|
||||||
|
*self.config.lock() = Some(FFIConfig {
|
||||||
|
callback_fn: unsafe { std::mem::transmute(init.callback_ptr) },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !self.initialized.load(Ordering::SeqCst) {
|
||||||
|
Err(FFIError::NotConfigured)?
|
||||||
|
}
|
||||||
|
|
||||||
|
match message {
|
||||||
|
proto::ffi_request::Message::AsyncConnect(connect) => {
|
||||||
|
self.async_runtime.spawn(room_task(connect));
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn livekit_ffi_request(data: *const u8, len: usize) {
|
||||||
|
let data = unsafe { slice::from_raw_parts(data, len) };
|
||||||
|
let request = proto::FfiRequest::decode(data).expect("Failed to decode the FFIRequest");
|
||||||
|
let res = FFI_SERVER.on_request_received(request.message.unwrap());
|
||||||
|
if let Err(err) = res {
|
||||||
|
eprintln!("failed to handle ffi request: {:?}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect a listen to Room events
|
||||||
|
async fn room_task(connect: proto::ConnectRequest) {
|
||||||
|
let res = Room::connect(&connect.url, &connect.token).await;
|
||||||
|
|
||||||
|
if res.is_err() {
|
||||||
|
let _ = FFI_SERVER.send_response(FFIResponse::AsyncConnect(proto::ConnectResponse {
|
||||||
|
success: false,
|
||||||
|
room: None,
|
||||||
|
}));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send connect response before listening to events
|
||||||
|
let (room, mut events) = res.unwrap();
|
||||||
|
let session = room.session();
|
||||||
|
|
||||||
|
let _ = FFI_SERVER.send_response(FFIResponse::AsyncConnect(proto::ConnectResponse {
|
||||||
|
success: true,
|
||||||
|
room: Some(proto::RoomInfo {
|
||||||
|
sid: session.sid(),
|
||||||
|
name: session.name(),
|
||||||
|
local_participant: Some((&room.session().local_participant()).into()),
|
||||||
|
participants: room
|
||||||
|
.session()
|
||||||
|
.participants()
|
||||||
|
.iter()
|
||||||
|
.map(|(_, p)| p.into())
|
||||||
|
.collect(),
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Listen to events
|
||||||
|
tokio::spawn(participant_task(Participant::Local(
|
||||||
|
session.local_participant(),
|
||||||
|
)));
|
||||||
|
|
||||||
|
while let Some(event) = events.recv().await {
|
||||||
|
if let Some(event) = proto::RoomEvent::from(session.sid(), event.clone()) {
|
||||||
|
let _ = FFI_SERVER.send_response(FFIResponse::RoomEvent(event));
|
||||||
|
}
|
||||||
|
|
||||||
|
match event {
|
||||||
|
RoomEvent::ParticipantConnected(p) => {
|
||||||
|
tokio::spawn(participant_task(Participant::Remote(p)));
|
||||||
|
}
|
||||||
|
RoomEvent::TrackSubscribed {
|
||||||
|
track,
|
||||||
|
publication,
|
||||||
|
participant,
|
||||||
|
} => {
|
||||||
|
if let RemoteTrackHandle::Video(video_track) = track {
|
||||||
|
let rtc_track = video_track.rtc_track();
|
||||||
|
rtc_track.on_frame(on_video_frame(video_track.sid()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen to participant events
|
||||||
|
async fn participant_task(participant: Participant) {
|
||||||
|
let mut participant_events = participant.register_observer();
|
||||||
|
while let Some(event) = participant_events.recv().await {
|
||||||
|
// TODO convert event to proto
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_video_frame(track_sid: TrackSid) -> OnFrameHandler {
|
||||||
|
Box::new(move |frame, buffer| {
|
||||||
|
let handle_id = FFI_SERVER.next_handle_id();
|
||||||
|
let proto_buffer = proto::VideoFrameBuffer::from(handle_id, &buffer);
|
||||||
|
FFI_SERVER.insert_handle(handle_id, Box::new(buffer));
|
||||||
|
|
||||||
|
let _ = FFI_SERVER.send_response(FFIResponse::TrackEvent(proto::TrackEvent {
|
||||||
|
track_sid: track_sid.to_string(),
|
||||||
|
message: Some(proto::track_event::Message::FrameReceived(
|
||||||
|
proto::FrameReceived {
|
||||||
|
frame: Some(frame.into()),
|
||||||
|
frame_buffer: Some(proto_buffer),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
}));
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -1,9 +1,26 @@
|
|||||||
|
use crate::video_frame_buffer::VideoFrameBuffer;
|
||||||
use cxx::UniquePtr;
|
use cxx::UniquePtr;
|
||||||
use webrtc_sys::video_frame as vf_sys;
|
use webrtc_sys::video_frame as vf_sys;
|
||||||
|
|
||||||
pub use vf_sys::ffi::VideoRotation;
|
#[derive(Debug)]
|
||||||
|
pub enum VideoRotation {
|
||||||
|
VideoRotation0 = 0,
|
||||||
|
VideoRotation90 = 90,
|
||||||
|
VideoRotation180 = 180,
|
||||||
|
VideoRotation270 = 270,
|
||||||
|
}
|
||||||
|
|
||||||
use crate::video_frame_buffer::VideoFrameBuffer;
|
impl From<vf_sys::ffi::VideoRotation> for VideoRotation {
|
||||||
|
fn from(rotation: vf_sys::ffi::VideoRotation) -> Self {
|
||||||
|
match rotation {
|
||||||
|
vf_sys::ffi::VideoRotation::VideoRotation0 => Self::VideoRotation0,
|
||||||
|
vf_sys::ffi::VideoRotation::VideoRotation90 => Self::VideoRotation90,
|
||||||
|
vf_sys::ffi::VideoRotation::VideoRotation180 => Self::VideoRotation180,
|
||||||
|
vf_sys::ffi::VideoRotation::VideoRotation270 => Self::VideoRotation270,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct VideoFrame {
|
pub struct VideoFrame {
|
||||||
cxx_handle: UniquePtr<vf_sys::ffi::VideoFrame>,
|
cxx_handle: UniquePtr<vf_sys::ffi::VideoFrame>,
|
||||||
@@ -47,7 +64,7 @@ impl VideoFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn rotation(&self) -> VideoRotation {
|
pub fn rotation(&self) -> VideoRotation {
|
||||||
self.cxx_handle.rotation()
|
self.cxx_handle.rotation().into()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// # Safety
|
/// # Safety
|
||||||
|
|||||||
@@ -2,10 +2,36 @@ use cxx::UniquePtr;
|
|||||||
use livekit_utils::enum_dispatch;
|
use livekit_utils::enum_dispatch;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::slice;
|
use std::slice;
|
||||||
use vfb_sys::ffi::VideoFrameBufferType;
|
|
||||||
use webrtc_sys::video_frame_buffer as vfb_sys;
|
use webrtc_sys::video_frame_buffer as vfb_sys;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum VideoFrameBufferType {
|
||||||
|
Native,
|
||||||
|
I420,
|
||||||
|
I420A,
|
||||||
|
I422,
|
||||||
|
I444,
|
||||||
|
I010,
|
||||||
|
NV12,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<vfb_sys::ffi::VideoFrameBufferType> for VideoFrameBufferType {
|
||||||
|
fn from(buffer_type: vfb_sys::ffi::VideoFrameBufferType) -> Self {
|
||||||
|
match buffer_type {
|
||||||
|
vfb_sys::ffi::VideoFrameBufferType::Native => Self::Native,
|
||||||
|
vfb_sys::ffi::VideoFrameBufferType::I420 => Self::I420,
|
||||||
|
vfb_sys::ffi::VideoFrameBufferType::I420A => Self::I420A,
|
||||||
|
vfb_sys::ffi::VideoFrameBufferType::I422 => Self::I422,
|
||||||
|
vfb_sys::ffi::VideoFrameBufferType::I444 => Self::I444,
|
||||||
|
vfb_sys::ffi::VideoFrameBufferType::I010 => Self::I010,
|
||||||
|
vfb_sys::ffi::VideoFrameBufferType::NV12 => Self::NV12,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait VideoFrameBufferTrait {
|
pub trait VideoFrameBufferTrait {
|
||||||
|
fn buffer_type(&self) -> VideoFrameBufferType; // Useful for the FFI
|
||||||
fn width(&self) -> i32;
|
fn width(&self) -> i32;
|
||||||
fn height(&self) -> i32;
|
fn height(&self) -> i32;
|
||||||
fn to_i420(self) -> I420Buffer;
|
fn to_i420(self) -> I420Buffer;
|
||||||
@@ -25,6 +51,24 @@ pub trait PlanarYuv8Buffer: PlanarYuvBuffer {
|
|||||||
fn data_v(&self) -> &[u8];
|
fn data_v(&self) -> &[u8];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait PlanarYuv16BBuffer: PlanarYuvBuffer {
|
||||||
|
fn data_y(&self) -> &[u16];
|
||||||
|
fn data_u(&self) -> &[u16];
|
||||||
|
fn data_v(&self) -> &[u16];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BiplanarYuvBuffer: VideoFrameBufferTrait {
|
||||||
|
fn chroma_width(&self) -> i32;
|
||||||
|
fn chroma_height(&self) -> i32;
|
||||||
|
fn stride_y(&self) -> i32;
|
||||||
|
fn stride_uv(&self) -> i32;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub trait BiplanarYuv8Buffer: BiplanarYuvBuffer {
|
||||||
|
fn data_y(&self) -> &[u8];
|
||||||
|
fn data_uv(&self) -> &[u8];
|
||||||
|
}
|
||||||
|
|
||||||
pub enum VideoFrameBuffer {
|
pub enum VideoFrameBuffer {
|
||||||
Native(NativeBuffer),
|
Native(NativeBuffer),
|
||||||
I420(I420Buffer),
|
I420(I420Buffer),
|
||||||
@@ -38,16 +82,26 @@ pub enum VideoFrameBuffer {
|
|||||||
impl VideoFrameBuffer {
|
impl VideoFrameBuffer {
|
||||||
pub(crate) fn new(mut cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
pub(crate) fn new(mut cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||||
unsafe {
|
unsafe {
|
||||||
match cxx_handle.buffer_type() {
|
match cxx_handle.buffer_type().into() {
|
||||||
VideoFrameBufferType::Native => Self::Native(NativeBuffer::new(cxx_handle)),
|
VideoFrameBufferType::Native => Self::Native(NativeBuffer::new(cxx_handle)),
|
||||||
VideoFrameBufferType::I420 => {
|
VideoFrameBufferType::I420 => {
|
||||||
Self::I420(I420Buffer::new(cxx_handle.pin_mut().get_i420()))
|
Self::I420(I420Buffer::new(cxx_handle.pin_mut().get_i420()))
|
||||||
}
|
}
|
||||||
VideoFrameBufferType::I420A => Self::I420A(I420ABuffer::new(cxx_handle)),
|
VideoFrameBufferType::I420A => {
|
||||||
VideoFrameBufferType::I422 => Self::I422(I422Buffer::new(cxx_handle)),
|
Self::I420A(I420ABuffer::new(cxx_handle.pin_mut().get_i420a()))
|
||||||
VideoFrameBufferType::I444 => Self::I444(I444Buffer::new(cxx_handle)),
|
}
|
||||||
VideoFrameBufferType::I010 => Self::I010(I010Buffer::new(cxx_handle)),
|
VideoFrameBufferType::I422 => {
|
||||||
VideoFrameBufferType::NV12 => Self::NV12(NV12Buffer::new(cxx_handle)),
|
Self::I422(I422Buffer::new(cxx_handle.pin_mut().get_i422()))
|
||||||
|
}
|
||||||
|
VideoFrameBufferType::I444 => {
|
||||||
|
Self::I444(I444Buffer::new(cxx_handle.pin_mut().get_i444()))
|
||||||
|
}
|
||||||
|
VideoFrameBufferType::I010 => {
|
||||||
|
Self::I010(I010Buffer::new(cxx_handle.pin_mut().get_i010()))
|
||||||
|
}
|
||||||
|
VideoFrameBufferType::NV12 => {
|
||||||
|
Self::NV12(NV12Buffer::new(cxx_handle.pin_mut().get_nv12()))
|
||||||
|
}
|
||||||
_ => unreachable!(), // VideoFrameBufferType is represented as i32
|
_ => unreachable!(), // VideoFrameBufferType is represented as i32
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -57,6 +111,7 @@ impl VideoFrameBuffer {
|
|||||||
impl VideoFrameBufferTrait for VideoFrameBuffer {
|
impl VideoFrameBufferTrait for VideoFrameBuffer {
|
||||||
enum_dispatch!(
|
enum_dispatch!(
|
||||||
[Native, I420, I420A, I422, I444, I010, NV12]
|
[Native, I420, I420A, I422, I444, I010, NV12]
|
||||||
|
fnc!(buffer_type, &Self, [], VideoFrameBufferType);
|
||||||
fnc!(width, &Self, [], i32);
|
fnc!(width, &Self, [], i32);
|
||||||
fnc!(height, &Self, [], i32);
|
fnc!(height, &Self, [], i32);
|
||||||
fnc!(to_i420, Self, [], I420Buffer);
|
fnc!(to_i420, Self, [], I420Buffer);
|
||||||
@@ -81,6 +136,13 @@ macro_rules! impl_video_frame_buffer {
|
|||||||
// Allow unused_unsafe when we don't do any cast ( e.g. NativeBuffer )
|
// Allow unused_unsafe when we don't do any cast ( e.g. NativeBuffer )
|
||||||
#[allow(unused_unsafe)]
|
#[allow(unused_unsafe)]
|
||||||
impl VideoFrameBufferTrait for $x {
|
impl VideoFrameBufferTrait for $x {
|
||||||
|
fn buffer_type(&self) -> VideoFrameBufferType {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
(*ptr).buffer_type().into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn width(&self) -> i32 {
|
fn width(&self) -> i32 {
|
||||||
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
unsafe {
|
unsafe {
|
||||||
@@ -179,6 +241,90 @@ macro_rules! impl_yuv8_buffer {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_yuv16_buffer {
|
||||||
|
($x:ty $(, $cast:ident)*) => {
|
||||||
|
impl PlanarYuv16BBuffer for $x {
|
||||||
|
fn data_y(&self) -> &[u16] {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
slice::from_raw_parts((*ptr).data_y(), (self.width() * self.height()) as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_u(&self) -> &[u16] {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
let chroma_height = (self.height() + 1) / 2;
|
||||||
|
slice::from_raw_parts((*ptr).data_u(), (self.stride_u() * chroma_height) as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_v(&self) -> &[u16] {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
let chroma_height = (self.height() + 1) / 2;
|
||||||
|
slice::from_raw_parts((*ptr).data_v(), (self.stride_v() * chroma_height) as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_biyuv_buffer {
|
||||||
|
($x:ty $(, $cast:ident)*) => {
|
||||||
|
impl BiplanarYuvBuffer for $x {
|
||||||
|
fn chroma_width(&self) -> i32 {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
(*ptr).chroma_width()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chroma_height(&self) -> i32 {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
(*ptr).chroma_height()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stride_y(&self) -> i32 {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
(*ptr).stride_y()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stride_uv(&self) -> i32 {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
(*ptr).stride_uv()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! impl_biyuv8_buffer {
|
||||||
|
($x:ty $(, $cast:ident)*) => {
|
||||||
|
impl BiplanarYuv8Buffer for $x {
|
||||||
|
fn data_y(&self) -> &[u8] {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
slice::from_raw_parts((*ptr).data_y(), (self.width() * self.height()) as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data_uv(&self) -> &[u8] {
|
||||||
|
let ptr = recursive_cast!(&*self.cxx_handle $(, $cast)*);
|
||||||
|
unsafe {
|
||||||
|
let chroma_height = (self.height() + 1) / 2;
|
||||||
|
slice::from_raw_parts((*ptr).data_uv(), (self.stride_uv() * chroma_height) as usize)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
pub struct NativeBuffer {
|
pub struct NativeBuffer {
|
||||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
||||||
}
|
}
|
||||||
@@ -188,36 +334,49 @@ pub struct I420Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct I420ABuffer {
|
pub struct I420ABuffer {
|
||||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
cxx_handle: UniquePtr<vfb_sys::ffi::I420ABuffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct I422Buffer {
|
pub struct I422Buffer {
|
||||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
cxx_handle: UniquePtr<vfb_sys::ffi::I422Buffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct I444Buffer {
|
pub struct I444Buffer {
|
||||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
cxx_handle: UniquePtr<vfb_sys::ffi::I444Buffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct I010Buffer {
|
pub struct I010Buffer {
|
||||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
cxx_handle: UniquePtr<vfb_sys::ffi::I010Buffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct NV12Buffer {
|
pub struct NV12Buffer {
|
||||||
cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>,
|
cxx_handle: UniquePtr<vfb_sys::ffi::NV12Buffer>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_video_frame_buffer!(NativeBuffer);
|
impl_video_frame_buffer!(NativeBuffer);
|
||||||
impl_video_frame_buffer!(I420Buffer, i420_to_yuv8, yuv8_to_yuv, yuv_to_vfb);
|
impl_video_frame_buffer!(I420Buffer, i420_to_yuv8, yuv8_to_yuv, yuv_to_vfb);
|
||||||
impl_video_frame_buffer!(I420ABuffer);
|
impl_video_frame_buffer!(I420ABuffer, i420a_to_yuv8, yuv8_to_yuv, yuv_to_vfb);
|
||||||
impl_video_frame_buffer!(I422Buffer);
|
impl_video_frame_buffer!(I422Buffer, i422_to_yuv8, yuv8_to_yuv, yuv_to_vfb);
|
||||||
impl_video_frame_buffer!(I444Buffer);
|
impl_video_frame_buffer!(I444Buffer, i444_to_yuv8, yuv8_to_yuv, yuv_to_vfb);
|
||||||
impl_video_frame_buffer!(I010Buffer);
|
impl_video_frame_buffer!(I010Buffer, i010_to_yuv16b, yuv16b_to_yuv, yuv_to_vfb);
|
||||||
impl_video_frame_buffer!(NV12Buffer);
|
impl_video_frame_buffer!(NV12Buffer, nv12_to_biyuv8, biyuv8_to_biyuv, biyuv_to_vfb);
|
||||||
|
|
||||||
impl_yuv_buffer!(I420Buffer, i420_to_yuv8, yuv8_to_yuv);
|
impl_yuv_buffer!(I420Buffer, i420_to_yuv8, yuv8_to_yuv);
|
||||||
|
impl_yuv_buffer!(I420ABuffer, i420a_to_yuv8, yuv8_to_yuv);
|
||||||
|
impl_yuv_buffer!(I422Buffer, i422_to_yuv8, yuv8_to_yuv);
|
||||||
|
impl_yuv_buffer!(I444Buffer, i444_to_yuv8, yuv8_to_yuv);
|
||||||
|
impl_yuv_buffer!(I010Buffer, i010_to_yuv16b, yuv16b_to_yuv);
|
||||||
|
|
||||||
impl_yuv8_buffer!(I420Buffer, i420_to_yuv8);
|
impl_yuv8_buffer!(I420Buffer, i420_to_yuv8);
|
||||||
|
impl_yuv8_buffer!(I420ABuffer, i420a_to_yuv8);
|
||||||
|
impl_yuv8_buffer!(I422Buffer, i422_to_yuv8);
|
||||||
|
impl_yuv8_buffer!(I444Buffer, i444_to_yuv8);
|
||||||
|
|
||||||
|
impl_yuv16_buffer!(I010Buffer, i010_to_yuv16b);
|
||||||
|
|
||||||
|
impl_biyuv_buffer!(NV12Buffer, nv12_to_biyuv8, biyuv8_to_biyuv);
|
||||||
|
|
||||||
|
impl_biyuv8_buffer!(NV12Buffer, nv12_to_biyuv8);
|
||||||
|
|
||||||
impl NativeBuffer {
|
impl NativeBuffer {
|
||||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
||||||
@@ -232,31 +391,31 @@ impl I420Buffer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl I420ABuffer {
|
impl I420ABuffer {
|
||||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::I420ABuffer>) -> Self {
|
||||||
Self { cxx_handle }
|
Self { cxx_handle }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl I422Buffer {
|
impl I422Buffer {
|
||||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::I422Buffer>) -> Self {
|
||||||
Self { cxx_handle }
|
Self { cxx_handle }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl I444Buffer {
|
impl I444Buffer {
|
||||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::I444Buffer>) -> Self {
|
||||||
Self { cxx_handle }
|
Self { cxx_handle }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl I010Buffer {
|
impl I010Buffer {
|
||||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::I010Buffer>) -> Self {
|
||||||
Self { cxx_handle }
|
Self { cxx_handle }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NV12Buffer {
|
impl NV12Buffer {
|
||||||
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::VideoFrameBuffer>) -> Self {
|
fn new(cxx_handle: UniquePtr<vfb_sys::ffi::NV12Buffer>) -> Self {
|
||||||
Self { cxx_handle }
|
Self { cxx_handle }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,4 +23,6 @@ pub use crate::webrtc::{
|
|||||||
},
|
},
|
||||||
rtp_receiver::RtpReceiver,
|
rtp_receiver::RtpReceiver,
|
||||||
rtp_transceiver::RtpTransceiver,
|
rtp_transceiver::RtpTransceiver,
|
||||||
|
video_frame::{VideoFrame, VideoRotation},
|
||||||
|
video_frame_buffer::{VideoFrameBuffer, VideoFrameBufferTrait, VideoFrameBufferType},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -49,8 +49,12 @@ pub struct ParticipantIdentity(String);
|
|||||||
#[derive(Clone, Default, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
#[derive(Clone, Default, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
pub struct TrackSid(String);
|
pub struct TrackSid(String);
|
||||||
|
|
||||||
|
#[derive(Clone, Default, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
|
||||||
|
pub struct RoomSid(String);
|
||||||
|
|
||||||
id_str! {
|
id_str! {
|
||||||
ParticipantSid;
|
ParticipantSid;
|
||||||
ParticipantIdentity;
|
ParticipantIdentity;
|
||||||
TrackSid;
|
TrackSid;
|
||||||
|
RoomSid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use crate::rtc_engine::{EngineEvent, EngineEvents, EngineResult, RTCEngine};
|
|||||||
use crate::signal_client::SignalOptions;
|
use crate::signal_client::SignalOptions;
|
||||||
use crate::{RoomError, RoomEvent, RoomResult, SimulateScenario};
|
use crate::{RoomError, RoomEvent, RoomResult, SimulateScenario};
|
||||||
use livekit_utils::observer::Dispatcher;
|
use livekit_utils::observer::Dispatcher;
|
||||||
use parking_lot::{Mutex, RwLock};
|
use parking_lot::{Mutex, RwLock, RwLockReadGuard};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::atomic::{AtomicU8, Ordering};
|
use std::sync::atomic::{AtomicU8, Ordering};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -149,8 +149,8 @@ impl RoomSession {
|
|||||||
self.inner.state.load(Ordering::Acquire).try_into().unwrap()
|
self.inner.state.load(Ordering::Acquire).try_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn participants(&self) -> &RwLock<HashMap<ParticipantSid, Arc<RemoteParticipant>>> {
|
pub fn participants(&self) -> RwLockReadGuard<HashMap<ParticipantSid, Arc<RemoteParticipant>>> {
|
||||||
&self.inner.participants
|
self.inner.participants.read()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> {
|
pub async fn simulate_scenario(&self, scenario: SimulateScenario) -> EngineResult<()> {
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use super::impl_track_trait;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -27,3 +28,5 @@ impl TryFrom<TrackHandle> for AudioTrackHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_track_trait!(AudioTrackHandle, enum_dispatch, [Local, Remote]);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use super::impl_track_trait;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -27,3 +28,5 @@ impl TryFrom<TrackHandle> for LocalTrackHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_track_trait!(LocalTrackHandle, enum_dispatch, [Audio, Video]);
|
||||||
|
|||||||
@@ -117,6 +117,7 @@ pub trait TrackTrait {
|
|||||||
fn name(&self) -> String;
|
fn name(&self) -> String;
|
||||||
fn kind(&self) -> TrackKind;
|
fn kind(&self) -> TrackKind;
|
||||||
fn stream_state(&self) -> StreamState;
|
fn stream_state(&self) -> StreamState;
|
||||||
|
fn muted(&self) -> bool;
|
||||||
fn start(&self);
|
fn start(&self);
|
||||||
fn stop(&self);
|
fn stop(&self);
|
||||||
fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent>;
|
fn register_observer(&self) -> mpsc::UnboundedReceiver<TrackEvent>;
|
||||||
@@ -201,6 +202,7 @@ impl TrackTrait for TrackHandle {
|
|||||||
fnc!(name, &Self, [], String);
|
fnc!(name, &Self, [], String);
|
||||||
fnc!(kind, &Self, [], TrackKind);
|
fnc!(kind, &Self, [], TrackKind);
|
||||||
fnc!(stream_state, &Self, [], StreamState);
|
fnc!(stream_state, &Self, [], StreamState);
|
||||||
|
fnc!(muted, &Self, [], bool);
|
||||||
fnc!(start, &Self, [], ());
|
fnc!(start, &Self, [], ());
|
||||||
fnc!(stop, &Self, [], ());
|
fnc!(stop, &Self, [], ());
|
||||||
fnc!(register_observer, &Self, [], mpsc::UnboundedReceiver<TrackEvent>);
|
fnc!(register_observer, &Self, [], mpsc::UnboundedReceiver<TrackEvent>);
|
||||||
@@ -246,6 +248,10 @@ macro_rules! impl_track_trait {
|
|||||||
self.shared.stream_state.load(Ordering::SeqCst).into()
|
self.shared.stream_state.load(Ordering::SeqCst).into()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn muted(&self) -> bool {
|
||||||
|
self.shared.muted.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
fn start(&self) {
|
fn start(&self) {
|
||||||
self.shared.start();
|
self.shared.start();
|
||||||
}
|
}
|
||||||
@@ -263,6 +269,25 @@ macro_rules! impl_track_trait {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
($x:ident, enum_dispatch, [$($variant:ident),+]) => {
|
||||||
|
use livekit_utils::enum_dispatch;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
impl TrackTrait for $x {
|
||||||
|
enum_dispatch!(
|
||||||
|
[$($variant),+]
|
||||||
|
fnc!(sid, &Self, [], TrackSid);
|
||||||
|
fnc!(name, &Self, [], String);
|
||||||
|
fnc!(kind, &Self, [], TrackKind);
|
||||||
|
fnc!(stream_state, &Self, [], StreamState);
|
||||||
|
fnc!(muted, &Self, [], bool);
|
||||||
|
fnc!(start, &Self, [], ());
|
||||||
|
fnc!(stop, &Self, [], ());
|
||||||
|
fnc!(register_observer, &Self, [], mpsc::UnboundedReceiver<TrackEvent>);
|
||||||
|
fnc!(set_muted, &Self, [muted: bool], ());
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) use impl_track_trait;
|
pub(super) use impl_track_trait;
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
|
use super::impl_track_trait;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use livekit_utils::enum_dispatch;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use tokio::sync::mpsc;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub enum RemoteTrackHandle {
|
pub enum RemoteTrackHandle {
|
||||||
@@ -9,20 +8,6 @@ pub enum RemoteTrackHandle {
|
|||||||
Video(Arc<RemoteVideoTrack>),
|
Video(Arc<RemoteVideoTrack>),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TrackTrait for RemoteTrackHandle {
|
|
||||||
enum_dispatch!(
|
|
||||||
[Audio, Video]
|
|
||||||
fnc!(sid, &Self, [], TrackSid);
|
|
||||||
fnc!(name, &Self, [], String);
|
|
||||||
fnc!(kind, &Self, [], TrackKind);
|
|
||||||
fnc!(stream_state, &Self, [], StreamState);
|
|
||||||
fnc!(start, &Self, [], ());
|
|
||||||
fnc!(stop, &Self, [], ());
|
|
||||||
fnc!(register_observer, &Self, [], mpsc::UnboundedReceiver<TrackEvent>);
|
|
||||||
fnc!(set_muted, &Self, [muted: bool], ());
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RemoteTrackHandle> for TrackHandle {
|
impl From<RemoteTrackHandle> for TrackHandle {
|
||||||
fn from(remote_track: RemoteTrackHandle) -> Self {
|
fn from(remote_track: RemoteTrackHandle) -> Self {
|
||||||
match remote_track {
|
match remote_track {
|
||||||
@@ -43,3 +28,5 @@ impl TryFrom<TrackHandle> for RemoteTrackHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_track_trait!(RemoteTrackHandle, enum_dispatch, [Audio, Video]);
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use super::impl_track_trait;
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
@@ -27,3 +28,5 @@ impl TryFrom<TrackHandle> for VideoTrackHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_track_trait!(VideoTrackHandle, enum_dispatch, [Local, Remote]);
|
||||||
|
|||||||
@@ -12,9 +12,12 @@
|
|||||||
|
|
||||||
namespace livekit {
|
namespace livekit {
|
||||||
|
|
||||||
class PlanarYuvBuffer;
|
|
||||||
class PlanarYuv8Buffer;
|
|
||||||
class I420Buffer;
|
class I420Buffer;
|
||||||
|
class I420ABuffer;
|
||||||
|
class I422Buffer;
|
||||||
|
class I444Buffer;
|
||||||
|
class I010Buffer;
|
||||||
|
class NV12Buffer;
|
||||||
|
|
||||||
class VideoFrameBuffer {
|
class VideoFrameBuffer {
|
||||||
public:
|
public:
|
||||||
@@ -32,13 +35,43 @@ class VideoFrameBuffer {
|
|||||||
return std::make_unique<I420Buffer>(buffer_->ToI420());
|
return std::make_unique<I420Buffer>(buffer_->ToI420());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const_cast is valid here because we take the ownership on the rust side
|
||||||
std::unique_ptr<I420Buffer> get_i420() {
|
std::unique_ptr<I420Buffer> get_i420() {
|
||||||
// const_cast is valid here because we take the ownership on the rust side
|
|
||||||
return std::make_unique<I420Buffer>(
|
return std::make_unique<I420Buffer>(
|
||||||
rtc::scoped_refptr<webrtc::I420BufferInterface>(
|
rtc::scoped_refptr<webrtc::I420BufferInterface>(
|
||||||
const_cast<webrtc::I420BufferInterface*>(buffer_->GetI420())));
|
const_cast<webrtc::I420BufferInterface*>(buffer_->GetI420())));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<I420ABuffer> get_i420a() {
|
||||||
|
return std::make_unique<I420ABuffer>(
|
||||||
|
rtc::scoped_refptr<webrtc::I420ABufferInterface>(
|
||||||
|
const_cast<webrtc::I420ABufferInterface*>(buffer_->GetI420A())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<I422Buffer> get_i422() {
|
||||||
|
return std::make_unique<I422Buffer>(
|
||||||
|
rtc::scoped_refptr<webrtc::I422BufferInterface>(
|
||||||
|
const_cast<webrtc::I422BufferInterface*>(buffer_->GetI422())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<I444Buffer> get_i444() {
|
||||||
|
return std::make_unique<I444Buffer>(
|
||||||
|
rtc::scoped_refptr<webrtc::I444BufferInterface>(
|
||||||
|
const_cast<webrtc::I444BufferInterface*>(buffer_->GetI444())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<I010Buffer> get_i010() {
|
||||||
|
return std::make_unique<I010Buffer>(
|
||||||
|
rtc::scoped_refptr<webrtc::I010BufferInterface>(
|
||||||
|
const_cast<webrtc::I010BufferInterface*>(buffer_->GetI010())));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<NV12Buffer> get_nv12() {
|
||||||
|
return std::make_unique<NV12Buffer>(
|
||||||
|
rtc::scoped_refptr<webrtc::NV12BufferInterface>(
|
||||||
|
const_cast<webrtc::NV12BufferInterface*>(buffer_->GetNV12())));
|
||||||
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer_;
|
rtc::scoped_refptr<webrtc::VideoFrameBuffer> buffer_;
|
||||||
};
|
};
|
||||||
@@ -76,24 +109,136 @@ class PlanarYuv8Buffer : public PlanarYuvBuffer {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class PlanarYuv16BBuffer : public PlanarYuvBuffer {
|
||||||
|
public:
|
||||||
|
explicit PlanarYuv16BBuffer(
|
||||||
|
rtc::scoped_refptr<webrtc::PlanarYuv16BBuffer> buffer)
|
||||||
|
: PlanarYuvBuffer(buffer) {}
|
||||||
|
|
||||||
|
const uint16_t* data_y() const { return buffer()->DataY(); }
|
||||||
|
const uint16_t* data_u() const { return buffer()->DataU(); }
|
||||||
|
const uint16_t* data_v() const { return buffer()->DataV(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
webrtc::PlanarYuv16BBuffer* buffer() const {
|
||||||
|
return static_cast<webrtc::PlanarYuv16BBuffer*>(buffer_.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BiplanarYuvBuffer : public VideoFrameBuffer {
|
||||||
|
public:
|
||||||
|
explicit BiplanarYuvBuffer(
|
||||||
|
rtc::scoped_refptr<webrtc::BiplanarYuvBuffer> buffer)
|
||||||
|
: VideoFrameBuffer(buffer) {}
|
||||||
|
|
||||||
|
int chroma_width() const { return buffer()->ChromaWidth(); }
|
||||||
|
int chroma_height() const { return buffer()->ChromaHeight(); }
|
||||||
|
|
||||||
|
int stride_y() const { return buffer()->StrideY(); }
|
||||||
|
int stride_uv() const { return buffer()->StrideUV(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
webrtc::BiplanarYuvBuffer* buffer() const {
|
||||||
|
return static_cast<webrtc::BiplanarYuvBuffer*>(buffer_.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class BiplanarYuv8Buffer : public BiplanarYuvBuffer {
|
||||||
|
public:
|
||||||
|
explicit BiplanarYuv8Buffer(
|
||||||
|
rtc::scoped_refptr<webrtc::BiplanarYuv8Buffer> buffer)
|
||||||
|
: BiplanarYuvBuffer(buffer) {}
|
||||||
|
|
||||||
|
const uint8_t* data_y() const { return buffer()->DataY(); }
|
||||||
|
const uint8_t* data_uv() const { return buffer()->DataUV(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
webrtc::BiplanarYuv8Buffer* buffer() const {
|
||||||
|
return static_cast<webrtc::BiplanarYuv8Buffer*>(buffer_.get());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class I420Buffer : public PlanarYuv8Buffer {
|
class I420Buffer : public PlanarYuv8Buffer {
|
||||||
public:
|
public:
|
||||||
explicit I420Buffer(rtc::scoped_refptr<webrtc::I420BufferInterface> buffer)
|
explicit I420Buffer(rtc::scoped_refptr<webrtc::I420BufferInterface> buffer)
|
||||||
: PlanarYuv8Buffer(buffer) {}
|
: PlanarYuv8Buffer(buffer) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class I420ABuffer : public I420Buffer {
|
||||||
|
public:
|
||||||
|
explicit I420ABuffer(rtc::scoped_refptr<webrtc::I420ABufferInterface> buffer)
|
||||||
|
: I420Buffer(buffer) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class I422Buffer : public PlanarYuv8Buffer {
|
||||||
|
public:
|
||||||
|
explicit I422Buffer(rtc::scoped_refptr<webrtc::I422BufferInterface> buffer)
|
||||||
|
: PlanarYuv8Buffer(buffer) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class I444Buffer : public PlanarYuv8Buffer {
|
||||||
|
public:
|
||||||
|
explicit I444Buffer(rtc::scoped_refptr<webrtc::I444BufferInterface> buffer)
|
||||||
|
: PlanarYuv8Buffer(buffer) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class I010Buffer : public PlanarYuv16BBuffer {
|
||||||
|
public:
|
||||||
|
explicit I010Buffer(rtc::scoped_refptr<webrtc::I010BufferInterface> buffer)
|
||||||
|
: PlanarYuv16BBuffer(buffer) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
class NV12Buffer : public BiplanarYuv8Buffer {
|
||||||
|
public:
|
||||||
|
explicit NV12Buffer(rtc::scoped_refptr<webrtc::NV12BufferInterface> buffer)
|
||||||
|
: BiplanarYuv8Buffer(buffer) {}
|
||||||
|
};
|
||||||
|
|
||||||
static const VideoFrameBuffer* yuv_to_vfb(const PlanarYuvBuffer* yuv) {
|
static const VideoFrameBuffer* yuv_to_vfb(const PlanarYuvBuffer* yuv) {
|
||||||
return yuv;
|
return yuv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const VideoFrameBuffer* biyuv_to_vfb(const BiplanarYuvBuffer* biyuv) {
|
||||||
|
return biyuv;
|
||||||
|
}
|
||||||
|
|
||||||
static const PlanarYuvBuffer* yuv8_to_yuv(const PlanarYuv8Buffer* yuv8) {
|
static const PlanarYuvBuffer* yuv8_to_yuv(const PlanarYuv8Buffer* yuv8) {
|
||||||
return yuv8;
|
return yuv8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const PlanarYuvBuffer* yuv16b_to_yuv(const PlanarYuv16BBuffer* yuv16) {
|
||||||
|
return yuv16;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const BiplanarYuvBuffer* biyuv8_to_biyuv(
|
||||||
|
const BiplanarYuv8Buffer* biyuv8) {
|
||||||
|
return biyuv8;
|
||||||
|
}
|
||||||
|
|
||||||
static const PlanarYuv8Buffer* i420_to_yuv8(const I420Buffer* i420) {
|
static const PlanarYuv8Buffer* i420_to_yuv8(const I420Buffer* i420) {
|
||||||
return i420;
|
return i420;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const PlanarYuv8Buffer* i420a_to_yuv8(const I420ABuffer* i420a) {
|
||||||
|
return i420a;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const PlanarYuv8Buffer* i422_to_yuv8(const I422Buffer* i422) {
|
||||||
|
return i422;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const PlanarYuv8Buffer* i444_to_yuv8(const I444Buffer* i444) {
|
||||||
|
return i444;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const PlanarYuv16BBuffer* i010_to_yuv16b(const I010Buffer* i010) {
|
||||||
|
return i010;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const BiplanarYuv8Buffer* nv12_to_biyuv8(const NV12Buffer* nv12) {
|
||||||
|
return nv12;
|
||||||
|
}
|
||||||
|
|
||||||
static std::unique_ptr<VideoFrameBuffer> _unique_video_frame_buffer() {
|
static std::unique_ptr<VideoFrameBuffer> _unique_video_frame_buffer() {
|
||||||
return nullptr; // Ignore
|
return nullptr; // Ignore
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,3 +15,16 @@ pub mod yuv_helper;
|
|||||||
pub const MEDIA_TYPE_VIDEO: &str = "video";
|
pub const MEDIA_TYPE_VIDEO: &str = "video";
|
||||||
pub const MEDIA_TYPE_AUDIO: &str = "audio";
|
pub const MEDIA_TYPE_AUDIO: &str = "audio";
|
||||||
pub const MEDIA_TYPE_DATA: &str = "data";
|
pub const MEDIA_TYPE_DATA: &str = "data";
|
||||||
|
|
||||||
|
macro_rules! impl_thread_safety {
|
||||||
|
($obj:ty, Send) => {
|
||||||
|
unsafe impl Send for $obj {}
|
||||||
|
};
|
||||||
|
|
||||||
|
($obj:ty, Send + Sync) => {
|
||||||
|
unsafe impl Send for $obj {}
|
||||||
|
unsafe impl Sync for $obj {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) use impl_thread_safety;
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use crate::impl_thread_safety;
|
||||||
|
|
||||||
#[cxx::bridge(namespace = "livekit")]
|
#[cxx::bridge(namespace = "livekit")]
|
||||||
pub mod ffi {
|
pub mod ffi {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -30,3 +32,5 @@ pub mod ffi {
|
|||||||
fn _unique_video_frame() -> UniquePtr<VideoFrame>; // Ignore
|
fn _unique_video_frame() -> UniquePtr<VideoFrame>; // Ignore
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_thread_safety!(ffi::VideoFrame, Send + Sync);
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use crate::impl_thread_safety;
|
||||||
|
|
||||||
#[cxx::bridge(namespace = "livekit")]
|
#[cxx::bridge(namespace = "livekit")]
|
||||||
pub mod ffi {
|
pub mod ffi {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -18,16 +20,29 @@ pub mod ffi {
|
|||||||
type VideoFrameBuffer;
|
type VideoFrameBuffer;
|
||||||
type PlanarYuvBuffer;
|
type PlanarYuvBuffer;
|
||||||
type PlanarYuv8Buffer;
|
type PlanarYuv8Buffer;
|
||||||
|
type PlanarYuv16BBuffer;
|
||||||
|
type BiplanarYuvBuffer;
|
||||||
|
type BiplanarYuv8Buffer;
|
||||||
type I420Buffer;
|
type I420Buffer;
|
||||||
|
type I420ABuffer;
|
||||||
|
type I422Buffer;
|
||||||
|
type I444Buffer;
|
||||||
|
type I010Buffer;
|
||||||
|
type NV12Buffer;
|
||||||
|
|
||||||
fn buffer_type(self: &VideoFrameBuffer) -> VideoFrameBufferType;
|
fn buffer_type(self: &VideoFrameBuffer) -> VideoFrameBufferType;
|
||||||
fn width(self: &VideoFrameBuffer) -> i32;
|
fn width(self: &VideoFrameBuffer) -> i32;
|
||||||
fn height(self: &VideoFrameBuffer) -> i32;
|
fn height(self: &VideoFrameBuffer) -> i32;
|
||||||
|
|
||||||
// Require ownership
|
/// # SAFETY
|
||||||
|
/// The functions require ownership
|
||||||
unsafe fn to_i420(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I420Buffer>;
|
unsafe fn to_i420(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I420Buffer>;
|
||||||
unsafe fn get_i420(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I420Buffer>;
|
unsafe fn get_i420(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I420Buffer>;
|
||||||
// TODO(theomonnom): Bridge other get_*
|
unsafe fn get_i420a(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I420ABuffer>;
|
||||||
|
unsafe fn get_i422(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I422Buffer>;
|
||||||
|
unsafe fn get_i444(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I444Buffer>;
|
||||||
|
unsafe fn get_i010(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<I010Buffer>;
|
||||||
|
unsafe fn get_nv12(self: Pin<&mut VideoFrameBuffer>) -> UniquePtr<NV12Buffer>;
|
||||||
|
|
||||||
fn chroma_width(self: &PlanarYuvBuffer) -> i32;
|
fn chroma_width(self: &PlanarYuvBuffer) -> i32;
|
||||||
fn chroma_height(self: &PlanarYuvBuffer) -> i32;
|
fn chroma_height(self: &PlanarYuvBuffer) -> i32;
|
||||||
@@ -39,10 +54,43 @@ pub mod ffi {
|
|||||||
fn data_u(self: &PlanarYuv8Buffer) -> *const u8;
|
fn data_u(self: &PlanarYuv8Buffer) -> *const u8;
|
||||||
fn data_v(self: &PlanarYuv8Buffer) -> *const u8;
|
fn data_v(self: &PlanarYuv8Buffer) -> *const u8;
|
||||||
|
|
||||||
|
fn data_y(self: &PlanarYuv16BBuffer) -> *const u16;
|
||||||
|
fn data_u(self: &PlanarYuv16BBuffer) -> *const u16;
|
||||||
|
fn data_v(self: &PlanarYuv16BBuffer) -> *const u16;
|
||||||
|
|
||||||
|
fn chroma_width(self: &BiplanarYuvBuffer) -> i32;
|
||||||
|
fn chroma_height(self: &BiplanarYuvBuffer) -> i32;
|
||||||
|
fn stride_y(self: &BiplanarYuvBuffer) -> i32;
|
||||||
|
fn stride_uv(self: &BiplanarYuvBuffer) -> i32;
|
||||||
|
|
||||||
|
fn data_y(self: &BiplanarYuv8Buffer) -> *const u8;
|
||||||
|
fn data_uv(self: &BiplanarYuv8Buffer) -> *const u8;
|
||||||
|
|
||||||
unsafe fn yuv_to_vfb(yuv: *const PlanarYuvBuffer) -> *const VideoFrameBuffer;
|
unsafe fn yuv_to_vfb(yuv: *const PlanarYuvBuffer) -> *const VideoFrameBuffer;
|
||||||
|
unsafe fn biyuv_to_vfb(yuv: *const BiplanarYuvBuffer) -> *const VideoFrameBuffer;
|
||||||
unsafe fn yuv8_to_yuv(yuv8: *const PlanarYuv8Buffer) -> *const PlanarYuvBuffer;
|
unsafe fn yuv8_to_yuv(yuv8: *const PlanarYuv8Buffer) -> *const PlanarYuvBuffer;
|
||||||
|
unsafe fn yuv16b_to_yuv(yuv16b: *const PlanarYuv16BBuffer) -> *const PlanarYuvBuffer;
|
||||||
|
unsafe fn biyuv8_to_biyuv(biyuv8: *const BiplanarYuv8Buffer) -> *const BiplanarYuvBuffer;
|
||||||
unsafe fn i420_to_yuv8(i420: *const I420Buffer) -> *const PlanarYuv8Buffer;
|
unsafe fn i420_to_yuv8(i420: *const I420Buffer) -> *const PlanarYuv8Buffer;
|
||||||
|
unsafe fn i420a_to_yuv8(i420a: *const I420ABuffer) -> *const PlanarYuv8Buffer;
|
||||||
|
unsafe fn i422_to_yuv8(i422: *const I422Buffer) -> *const PlanarYuv8Buffer;
|
||||||
|
unsafe fn i444_to_yuv8(i444: *const I444Buffer) -> *const PlanarYuv8Buffer;
|
||||||
|
unsafe fn i010_to_yuv16b(i010: *const I010Buffer) -> *const PlanarYuv16BBuffer;
|
||||||
|
unsafe fn nv12_to_biyuv8(nv12: *const NV12Buffer) -> *const BiplanarYuv8Buffer;
|
||||||
|
|
||||||
fn _unique_video_frame_buffer() -> UniquePtr<VideoFrameBuffer>;
|
fn _unique_video_frame_buffer() -> UniquePtr<VideoFrameBuffer>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl_thread_safety!(ffi::VideoFrameBuffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::PlanarYuvBuffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::PlanarYuv8Buffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::PlanarYuv16BBuffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::BiplanarYuvBuffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::BiplanarYuv8Buffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::I420Buffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::I420ABuffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::I422Buffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::I444Buffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::I010Buffer, Send + Sync);
|
||||||
|
impl_thread_safety!(ffi::NV12Buffer, Send + Sync);
|
||||||
|
|||||||
Reference in New Issue
Block a user